package com.ib.algokit.marketscan;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.activequant.core.domainmodel.marketscanner.InstrumentSpecificationDetails;
import org.activequant.core.domainmodel.marketscanner.MarketScan;
import org.activequant.core.domainmodel.marketscanner.MarketScanChange;
import org.activequant.core.domainmodel.marketscanner.MarketScanChange.ChangeType;
import org.activequant.core.domainmodel.marketscanner.MarketScanRow;
import org.activequant.core.types.Triple;
import org.activequant.util.pattern.events.Event;
import org.activequant.util.pattern.events.IEventListener;
import org.apache.log4j.Logger;

public class MarketScanChangeEventDetectionStrategy implements IMarketScanEventProcessingStrategy{
	protected final static Logger log = Logger.getLogger(MarketScanChangeEventDetectionStrategy.class);
	/**
	 * private final Event&lt;MarketScanChange&gt; marketScanInstrumentAddedEvent = new Event&lt;MarketScanChange&gt;();
	 */
	private final Event<MarketScanChange> marketScanInstrumentAddedEvent = new Event<MarketScanChange>();
	/**
	 * private final Event&lt;MarketScanChange&gt; marketScanInstrumentRemovedEvent = new Event&lt;MarketScanChange&gt;();
	 */
	private final Event<MarketScanChange> marketScanInstrumentRemovedEvent = new Event<MarketScanChange>();
	/**
	 * private final Event&lt;MarketScanChange&gt; marketScanInstrumentMovedUpEvent = new Event&lt;MarketScanChange&gt;();
	 */
	private final Event<MarketScanChange> marketScanInstrumentMovedUpEvent = new Event<MarketScanChange>();
	/**
	 * private final Event&lt;MarketScanChange&gt; marketScanInstrumentMovedDownEvent = new Event&lt;MarketScanChange&gt;();
	 */
	private final Event<MarketScanChange> marketScanInstrumentMovedDownEvent = new Event<MarketScanChange>();
	/**
	 * private boolean hasInstrumentAddedEventListeners=false;
	 */
	private boolean hasInstrumentAddedEventListeners=false;
	/**
	 * private boolean hasInstrumentRemovedEventListeners=false;
	 */
	private boolean hasInstrumentRemovedEventListeners=false;
	/**
	 * private boolean hasInstrumentMovedUpEventListeners=false;
	 */
	private boolean hasInstrumentMovedUpEventListeners=false;
	/**
	 * private boolean hasInstrumentMovedDownEventListeners=false;
	 */
	private boolean hasInstrumentMovedDownEventListeners=false;
	
	
	/**
	 * This method detects changes(added, removed, moved up or moved down) between the given newScan(MarketScan) and previousScan(MarketScan) and notifies interested listeners 
	 * by firing the appropriate event:<br/>
	 * <strong>1.</strong> If it doesn't have at least one previous scan (2 altogether), it returns without doing anything else.<br/>
	 * <strong>2.</strong> Finds the MarketScanRows who exist in the new scan but not in the previous scan. It also finds the MarketScanRows who existed in the previous scan but not in the new one.
	 * <strong>3.</strong> Then (if there were any changes) it finds out which Instruments are new/added and which are gone/removed, when compared to the previous MarketScan
	 */
	public void processNewMarketScan(MarketScan previousScan, MarketScan newScan) {
		//don't do anything if we aren't initialized yet
		if(previousScan==null||newScan==null){
			return;
		}
		if(!(hasInstrumentAddedEventListeners||hasInstrumentRemovedEventListeners||hasInstrumentMovedUpEventListeners||hasInstrumentMovedDownEventListeners)){
			return;
		}
		List<MarketScanRow> prevRows = previousScan.getRows();
		List<MarketScanRow> newRows = newScan.getRows();
		
		//find all MarketScanRow's in the new scan that didn't exist in the previous one		
		List<MarketScanRow> newMarketScanRows=new ArrayList<MarketScanRow>();
		newMarketScanRows.addAll(newRows);
		newMarketScanRows.removeAll(prevRows);	
		
		//find all MarketScanRow's in the previous scan that don't exist in the new one	
		List<MarketScanRow> removedMarketScanRows=new ArrayList<MarketScanRow>();
		removedMarketScanRows.addAll(prevRows);
		removedMarketScanRows.removeAll(newRows);
		
		if(newMarketScanRows.size()!=0||removedMarketScanRows.size()!=0){
			int numberOfInstruments=newScan.getRows().size();
			long changeInterval=(newScan.getTimeStamp().getNanoseconds()-previousScan.getTimeStamp().getNanoseconds())/1000000L;		
			Set<InstrumentSpecificationDetails> newOrShiftedInstrumentSpecificationDetails=new HashSet<InstrumentSpecificationDetails>();
			Set<InstrumentSpecificationDetails> removedOrShiftedInstrumentSpecificationDetails=new HashSet<InstrumentSpecificationDetails>();				
			for(MarketScanRow newMarketScanRow:newMarketScanRows){
				newOrShiftedInstrumentSpecificationDetails.add(newMarketScanRow.getInstrumentSpecificationDetails());					
				//log.info("new="+newMarketScanRow.getInstrumentSpecificationDetails().getInstrumentSpecification().getSymbol()+" at #"+newMarketScanRow.getRanking());
			}
			for(MarketScanRow removedMarketScanRow:removedMarketScanRows){
				removedOrShiftedInstrumentSpecificationDetails.add(removedMarketScanRow.getInstrumentSpecificationDetails());
				//log.info("removed="+removedMarketScanRow.getInstrumentSpecificationDetails().getInstrumentSpecification().getSymbol()+" from #"+removedMarketScanRow.getRanking());
			}
			
			Set<InstrumentSpecificationDetails> newInstrumentSpecificationDetails=new HashSet<InstrumentSpecificationDetails>();//not just shifted
			newInstrumentSpecificationDetails.addAll(newOrShiftedInstrumentSpecificationDetails);
			newInstrumentSpecificationDetails.removeAll(removedOrShiftedInstrumentSpecificationDetails);
			
			Set<InstrumentSpecificationDetails> removedInstrumentSpecificationDetails=new HashSet<InstrumentSpecificationDetails>();//not just shifted
			removedInstrumentSpecificationDetails.addAll(removedOrShiftedInstrumentSpecificationDetails);
			removedInstrumentSpecificationDetails.removeAll(newOrShiftedInstrumentSpecificationDetails);
						
			Set<InstrumentSpecificationDetails> shiftedInstrumentSpecificationDetails=new HashSet<InstrumentSpecificationDetails>();
			shiftedInstrumentSpecificationDetails.addAll(removedOrShiftedInstrumentSpecificationDetails);
			shiftedInstrumentSpecificationDetails.addAll(newOrShiftedInstrumentSpecificationDetails);
			shiftedInstrumentSpecificationDetails.removeAll(newInstrumentSpecificationDetails);
			shiftedInstrumentSpecificationDetails.removeAll(removedInstrumentSpecificationDetails);
			
			
			List<Triple<InstrumentSpecificationDetails,Integer,Integer>> pendingShifts= new ArrayList<Triple<InstrumentSpecificationDetails,Integer,Integer>>();
			
//			for(MarketScanRow prevRow:prevRows){
			for(int i=0;i<prevRows.size();i++){
				MarketScanRow prevRow=prevRows.get(i);	
				InstrumentSpecificationDetails instrumentSpecificationDetails=prevRow.getInstrumentSpecificationDetails();
				if(removedInstrumentSpecificationDetails.contains(instrumentSpecificationDetails)){					
					int removedFrom=i;
					MarketScanChange removedChange=new MarketScanChange();
					removedChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
					removedChange.setPreviousPosition(removedFrom);
					removedChange.setScanCriteria(newScan.getScanCriteria());
					removedChange.setTimeStamp(newScan.getTimeStamp());
					removedChange.setPreviousScanTime(previousScan.getTimeStamp());
					removedChange.setNumberOfInstruments(numberOfInstruments);
					removedChange.setChangeInterval(changeInterval);
					removedChange.setAmplitude(numberOfInstruments-removedFrom);
					removedChange.setMagnitude(((double)removedChange.getAmplitude()/(double)numberOfInstruments));
					removedChange.setChangeType(ChangeType.INSTRUMENT_REMOVED);
					marketScanInstrumentRemovedEvent.fire(removedChange);
				}else if(shiftedInstrumentSpecificationDetails.contains(instrumentSpecificationDetails)){
					int shiftedFrom=i;
					boolean foundSpec=false;
					for(Triple<InstrumentSpecificationDetails,Integer,Integer> pendingShift:pendingShifts){
						if(pendingShift.getObject1().equals(instrumentSpecificationDetails)){
							pendingShift.setObject2(shiftedFrom);
							if(pendingShift.getObject3()!=null){
								int shiftedTo=pendingShift.getObject3();
								if(shiftedFrom>shiftedTo){
									MarketScanChange movedUpChange=new MarketScanChange();
									movedUpChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
									movedUpChange.setPreviousPosition(shiftedFrom);
									movedUpChange.setCurrentPosition(shiftedTo);
									movedUpChange.setScanCriteria(newScan.getScanCriteria());
									movedUpChange.setTimeStamp(newScan.getTimeStamp());
									movedUpChange.setPreviousScanTime(previousScan.getTimeStamp());
									movedUpChange.setNumberOfInstruments(numberOfInstruments);									
									movedUpChange.setChangeInterval(changeInterval);									
									movedUpChange.setAmplitude(shiftedFrom-shiftedTo);
									movedUpChange.setMagnitude(((double)movedUpChange.getAmplitude()/(double)numberOfInstruments));
									movedUpChange.setChangeType(ChangeType.INSTRUMENT_MOVED_UP);
									marketScanInstrumentMovedUpEvent.fire(movedUpChange);
								}else{
									MarketScanChange movedDownChange=new MarketScanChange();
									movedDownChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
									movedDownChange.setPreviousPosition(shiftedFrom);
									movedDownChange.setCurrentPosition(shiftedTo);
									movedDownChange.setScanCriteria(newScan.getScanCriteria());
									movedDownChange.setTimeStamp(newScan.getTimeStamp());
									movedDownChange.setPreviousScanTime(previousScan.getTimeStamp());
									movedDownChange.setNumberOfInstruments(numberOfInstruments);									
									movedDownChange.setChangeInterval(changeInterval);
									movedDownChange.setAmplitude(shiftedTo-shiftedFrom);
									movedDownChange.setMagnitude(((double)movedDownChange.getAmplitude()/(double)numberOfInstruments));
									movedDownChange.setChangeType(ChangeType.INSTRUMENT_MOVED_DOWN);
									marketScanInstrumentMovedDownEvent.fire(movedDownChange);
								}
								pendingShifts.remove(pendingShift);
							}
							foundSpec=true;
							break;
						}
					}
					if(!foundSpec){
						Triple<InstrumentSpecificationDetails,Integer,Integer> pendingShift=new Triple<InstrumentSpecificationDetails,Integer,Integer>(instrumentSpecificationDetails,shiftedFrom,null);
						pendingShifts.add(pendingShift);
					}					
				}
			}
			
//			for(MarketScanRow newRow:newRows){
			for(int i=0;i<newRows.size();i++){
				MarketScanRow newRow=newRows.get(i);	
				InstrumentSpecificationDetails instrumentSpecificationDetails=newRow.getInstrumentSpecificationDetails();
				if(newInstrumentSpecificationDetails.contains(instrumentSpecificationDetails)){
					int addedTo=i;	
					MarketScanChange addedChange=new MarketScanChange();
					addedChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
					addedChange.setCurrentPosition(addedTo);
					addedChange.setScanCriteria(newScan.getScanCriteria());
					addedChange.setTimeStamp(newScan.getTimeStamp());
					addedChange.setPreviousScanTime(previousScan.getTimeStamp());
					addedChange.setNumberOfInstruments(numberOfInstruments);
					addedChange.setChangeInterval(changeInterval);
					addedChange.setAmplitude(numberOfInstruments-addedTo);
					addedChange.setMagnitude(((double)addedChange.getAmplitude()/(double)numberOfInstruments));
					addedChange.setChangeType(ChangeType.INSTRUMENT_ADDED);
					marketScanInstrumentAddedEvent.fire(addedChange);
				}else if(shiftedInstrumentSpecificationDetails.contains(instrumentSpecificationDetails)){
					int shiftedTo=i;	
					boolean foundSpec=false;
					for(Triple<InstrumentSpecificationDetails,Integer,Integer> pendingShift:pendingShifts){
						if(pendingShift.getObject1().equals(instrumentSpecificationDetails)){
							pendingShift.setObject3(shiftedTo);
							if(pendingShift.getObject2()!=null){
								int shiftedFrom=pendingShift.getObject2();
								if(shiftedFrom>shiftedTo){
									MarketScanChange movedUpChange=new MarketScanChange();
									movedUpChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
									movedUpChange.setPreviousPosition(shiftedFrom);
									movedUpChange.setCurrentPosition(shiftedTo);
									movedUpChange.setScanCriteria(newScan.getScanCriteria());
									movedUpChange.setTimeStamp(newScan.getTimeStamp());
									movedUpChange.setPreviousScanTime(previousScan.getTimeStamp());
									movedUpChange.setNumberOfInstruments(numberOfInstruments);
									movedUpChange.setChangeInterval(changeInterval);
									movedUpChange.setAmplitude(shiftedFrom-shiftedTo);
									movedUpChange.setMagnitude(((double)movedUpChange.getAmplitude()/(double)numberOfInstruments));
									movedUpChange.setChangeType(ChangeType.INSTRUMENT_MOVED_UP);
									marketScanInstrumentMovedUpEvent.fire(movedUpChange);
								}else{
									MarketScanChange movedDownChange=new MarketScanChange();
									movedDownChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
									movedDownChange.setPreviousPosition(shiftedFrom);
									movedDownChange.setCurrentPosition(shiftedTo);
									movedDownChange.setScanCriteria(newScan.getScanCriteria());
									movedDownChange.setTimeStamp(newScan.getTimeStamp());
									movedDownChange.setPreviousScanTime(previousScan.getTimeStamp());
									movedDownChange.setNumberOfInstruments(numberOfInstruments);
									movedDownChange.setChangeInterval(changeInterval);
									movedDownChange.setAmplitude(shiftedTo-shiftedFrom);
									movedDownChange.setMagnitude(((double)movedDownChange.getAmplitude()/(double)numberOfInstruments));
									movedDownChange.setChangeType(ChangeType.INSTRUMENT_MOVED_DOWN);
									marketScanInstrumentMovedDownEvent.fire(movedDownChange);
								}
								pendingShifts.remove(pendingShift);
							}
							foundSpec=true;
							break;
						}
					}
					if(!foundSpec){
						Triple<InstrumentSpecificationDetails,Integer,Integer> pendingShift=new Triple<InstrumentSpecificationDetails,Integer,Integer>(instrumentSpecificationDetails,null,shiftedTo);
						pendingShifts.add(pendingShift);
					}
				}				
			}
		}
		
	}
	/**
	 * Subscribes the given listener(IEventListener&lt;MarketScanChange&gt;) so it gets notified on MarketScanChange events
	 */
	public void subscribeForInstrumentAddedEvents(final IEventListener<MarketScanChange> listener){
		marketScanInstrumentAddedEvent.addEventListener(listener);
		hasInstrumentAddedEventListeners=true;
	}
	/**
	 * Unsubscribes the given listener(IEventListener&lt;MarketScanChange&gt;) so it doesn't get notified on MarketScanChange events any longer
	 */
	public void unsubscribeFromInstrumentAddedEvents(final IEventListener<MarketScanChange> listener){
		marketScanInstrumentAddedEvent.removeEventListener(listener);
		if(marketScanInstrumentAddedEvent.isEmpty()){
			hasInstrumentAddedEventListeners=false;
		}
	}
	/**
	 * Subscribes the given listener(IEventListener&lt;MarketScanChange&gt;) so it gets notified on MarketScanChange events
	 */
	public void subscribeForInstrumentRemovedEvents(final IEventListener<MarketScanChange> listener){
		marketScanInstrumentRemovedEvent.addEventListener(listener);
		hasInstrumentRemovedEventListeners=true;
	}
	/**
	 * Unsubscribes the given listener(IEventListener&lt;MarketScanChange&gt;) so it doesn't get notified on MarketScanChange events any longer
	 */
	public void unsubscribeFromInstrumentRemovedEvents(final IEventListener<MarketScanChange> listener){
		marketScanInstrumentRemovedEvent.removeEventListener(listener);
		if(marketScanInstrumentRemovedEvent.isEmpty()){
			hasInstrumentRemovedEventListeners=false;
		}
	}
	/**
	 * Subscribes the given listener(IEventListener&lt;MarketScanChange&gt;) so it gets notified on MarketScanChange events
	 */
	public void subscribeForInstrumentMovedUpEvents(final IEventListener<MarketScanChange> listener){
		marketScanInstrumentMovedUpEvent.addEventListener(listener);
		hasInstrumentMovedUpEventListeners=true;
	}
	/**
	 * Unsubscribes the given listener(IEventListener&lt;MarketScanChange&gt;) so it doesn't get notified on MarketScanChange events any longer
	 */
	public void unsubscribeFromInstrumentMovedUpEvents(final IEventListener<MarketScanChange> listener){
		marketScanInstrumentMovedUpEvent.removeEventListener(listener);
		if(marketScanInstrumentMovedUpEvent.isEmpty()){
			hasInstrumentMovedUpEventListeners=false;
		}
	}
	/**
	 * Subscribes the given listener(IEventListener&lt;MarketScanChange&gt;) so it gets notified on MarketScanChange events
	 */
	public void subscribeForInstrumentMovedDownEvents(final IEventListener<MarketScanChange> listener){
		marketScanInstrumentMovedDownEvent.addEventListener(listener);
		hasInstrumentMovedDownEventListeners=true;
	}
	/**
	 * Unsubscribes the given listener(IEventListener&lt;MarketScanChange&gt;) so it doesn't get notified on MarketScanChange events any longer
	 */
	public void unsubscribeFromInstrumentMovedDownEvents(final IEventListener<MarketScanChange> listener){
		marketScanInstrumentMovedDownEvent.removeEventListener(listener);
		if(marketScanInstrumentMovedDownEvent.isEmpty()){
			hasInstrumentMovedDownEventListeners=false;
		}
	}
}
